
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import { __extends } from 'tslib';
import { bind, each, indexOf, curry, extend, normalizeCssArray, isFunction } from 'zrender/lib/core/util.js';
import * as graphic from '../../util/graphic.js';
import { getECData } from '../../util/innerStore.js';
import { isHighDownDispatcher, setAsHighDownDispatcher, setDefaultStateProxy, enableHoverFocus, Z2_EMPHASIS_LIFT } from '../../util/states.js';
import DataDiffer from '../../data/DataDiffer.js';
import * as helper from '../helper/treeHelper.js';
import Breadcrumb from './Breadcrumb.js';
import RoamController from '../../component/helper/RoamController.js';
import BoundingRect from 'zrender/lib/core/BoundingRect.js';
import * as matrix from 'zrender/lib/core/matrix.js';
import * as animationUtil from '../../util/animation.js';
import makeStyleMapper from '../../model/mixin/makeStyleMapper.js';
import ChartView from '../../view/Chart.js';
import Displayable from 'zrender/lib/graphic/Displayable.js';
import { makeInner, convertOptionIdName } from '../../util/model.js';
import { windowOpen } from '../../util/format.js';
import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle.js';
var Group = graphic.Group;
var Rect = graphic.Rect;
var DRAG_THRESHOLD = 3;
var PATH_LABEL_NOAMAL = 'label';
var PATH_UPPERLABEL_NORMAL = 'upperLabel'; // Should larger than emphasis states lift z

var Z2_BASE = Z2_EMPHASIS_LIFT * 10; // Should bigger than every z2.

var Z2_BG = Z2_EMPHASIS_LIFT * 2;
var Z2_CONTENT = Z2_EMPHASIS_LIFT * 3;
var getStateItemStyle = makeStyleMapper([['fill', 'color'], // `borderColor` and `borderWidth` has been occupied,
// so use `stroke` to indicate the stroke of the rect.
	['stroke', 'strokeColor'], ['lineWidth', 'strokeWidth'], ['shadowBlur'], ['shadowOffsetX'], ['shadowOffsetY'], ['shadowColor'] // Option decal is in `DecalObject` but style.decal is in `PatternObject`.
// So do not transfer decal directly.
]);

var getItemStyleNormal = function (model) {
	// Normal style props should include emphasis style props.
	var itemStyle = getStateItemStyle(model); // Clear styles set by emphasis.

	itemStyle.stroke = itemStyle.fill = itemStyle.lineWidth = null;
	return itemStyle;
};

var inner = makeInner();

var TreemapView =
/** @class */
function (_super) {
	__extends(TreemapView, _super);

	function TreemapView() {
		var _this = _super !== null && _super.apply(this, arguments) || this;

		_this.type = TreemapView.type;
		_this._state = 'ready';
		_this._storage = createStorage();
		return _this;
	}
	/**
   * @override
   */

	TreemapView.prototype.render = function (seriesModel, ecModel, api, payload) {
		var models = ecModel.findComponents({
			mainType: 'series',
			subType: 'treemap',
			query: payload
		});

		if (indexOf(models, seriesModel) < 0) {
			return;
		}

		this.seriesModel = seriesModel;
		this.api = api;
		this.ecModel = ecModel;
		var types = ['treemapZoomToNode', 'treemapRootToNode'];
		var targetInfo = helper.retrieveTargetInfo(payload, types, seriesModel);
		var payloadType = payload && payload.type;
		var layoutInfo = seriesModel.layoutInfo;
		var isInit = !this._oldTree;
		var thisStorage = this._storage; // Mark new root when action is treemapRootToNode.

		var reRoot = payloadType === 'treemapRootToNode' && targetInfo && thisStorage ? {
			rootNodeGroup: thisStorage.nodeGroup[targetInfo.node.getRawIndex()],
			direction: payload.direction
		} : null;

		var containerGroup = this._giveContainerGroup(layoutInfo);

		var hasAnimation = seriesModel.get('animation');

		var renderResult = this._doRender(containerGroup, seriesModel, reRoot);

		hasAnimation && !isInit && (!payloadType || payloadType === 'treemapZoomToNode' || payloadType === 'treemapRootToNode') ? this._doAnimation(containerGroup, renderResult, seriesModel, reRoot) : renderResult.renderFinally();

		this._resetController(api);

		this._renderBreadcrumb(seriesModel, api, targetInfo);
	};

	TreemapView.prototype._giveContainerGroup = function (layoutInfo) {
		var containerGroup = this._containerGroup;

		if (!containerGroup) {
			// FIXME
			// 加一层containerGroup是为了clip，但是现在clip功能并没有实现。
			containerGroup = this._containerGroup = new Group();

			this._initEvents(containerGroup);

			this.group.add(containerGroup);
		}

		containerGroup.x = layoutInfo.x;
		containerGroup.y = layoutInfo.y;
		return containerGroup;
	};

	TreemapView.prototype._doRender = function (containerGroup, seriesModel, reRoot) {
		var thisTree = seriesModel.getData().tree;
		var oldTree = this._oldTree; // Clear last shape records.

		var lastsForAnimation = createStorage();
		var thisStorage = createStorage();
		var oldStorage = this._storage;
		var willInvisibleEls = [];

		function doRenderNode(thisNode, oldNode, parentGroup, depth) {
			return renderNode(seriesModel, thisStorage, oldStorage, reRoot, lastsForAnimation, willInvisibleEls, thisNode, oldNode, parentGroup, depth);
		} // Notice: When thisTree and oldTree are the same tree (see list.cloneShallow),
		// the oldTree is actually losted, so we cannot find all of the old graphic
		// elements from tree. So we use this strategy: make element storage, move
		// from old storage to new storage, clear old storage.

		dualTravel(thisTree.root ? [thisTree.root] : [], oldTree && oldTree.root ? [oldTree.root] : [], containerGroup, thisTree === oldTree || !oldTree, 0); // Process all removing.

		var willDeleteEls = clearStorage(oldStorage);
		this._oldTree = thisTree;
		this._storage = thisStorage;
		return {
			lastsForAnimation: lastsForAnimation,
			willDeleteEls: willDeleteEls,
			renderFinally: renderFinally
		};

		function dualTravel(thisViewChildren, oldViewChildren, parentGroup, sameTree, depth) {
			// When 'render' is triggered by action,
			// 'this' and 'old' may be the same tree,
			// we use rawIndex in that case.
			if (sameTree) {
				oldViewChildren = thisViewChildren;
				each(thisViewChildren, function (child, index) {
					!child.isRemoved() && processNode(index, index);
				});
			} // Diff hierarchically (diff only in each subtree, but not whole).
			// because, consistency of view is important.
			else {
				new DataDiffer(oldViewChildren, thisViewChildren, getKey, getKey).add(processNode).update(processNode).remove(curry(processNode, null)).execute();
			}

			function getKey(node) {
				// Identify by name or raw index.
				return node.getId();
			}

			function processNode(newIndex, oldIndex) {
				var thisNode = newIndex != null ? thisViewChildren[newIndex] : null;
				var oldNode = oldIndex != null ? oldViewChildren[oldIndex] : null;
				var group = doRenderNode(thisNode, oldNode, parentGroup, depth);
				group && dualTravel(thisNode && thisNode.viewChildren || [], oldNode && oldNode.viewChildren || [], group, sameTree, depth + 1);
			}
		}

		function clearStorage(storage) {
			var willDeleteEls = createStorage();
			storage && each(storage, function (store, storageName) {
				var delEls = willDeleteEls[storageName];
				each(store, function (el) {
					el && (delEls.push(el), inner(el).willDelete = true);
				});
			});
			return willDeleteEls;
		}

		function renderFinally() {
			each(willDeleteEls, function (els) {
				each(els, function (el) {
					el.parent && el.parent.remove(el);
				});
			});
			each(willInvisibleEls, function (el) {
				el.invisible = true; // Setting invisible is for optimizing, so no need to set dirty,
				// just mark as invisible.

				el.dirty();
			});
		}
	};

	TreemapView.prototype._doAnimation = function (containerGroup, renderResult, seriesModel, reRoot) {
		var durationOption = seriesModel.get('animationDurationUpdate');
		var easingOption = seriesModel.get('animationEasing'); // TODO: do not support function until necessary.

		var duration = (isFunction(durationOption) ? 0 : durationOption) || 0;
		var easing = (isFunction(easingOption) ? null : easingOption) || 'cubicOut';
		var animationWrap = animationUtil.createWrap(); // Make delete animations.

		each(renderResult.willDeleteEls, function (store, storageName) {
			each(store, function (el, rawIndex) {
				if (el.invisible) {
					return;
				}

				var parent = el.parent; // Always has parent, and parent is nodeGroup.

				var target;
				var innerStore = inner(parent);

				if (reRoot && reRoot.direction === 'drillDown') {
					target = parent === reRoot.rootNodeGroup // This is the content element of view root.
					// Only `content` will enter this branch, because
					// `background` and `nodeGroup` will not be deleted.
						? {
							shape: {
								x: 0,
								y: 0,
								width: innerStore.nodeWidth,
								height: innerStore.nodeHeight
							},
							style: {
								opacity: 0
							}
						} // Others.
						: {
							style: {
								opacity: 0
							}
						};
				} else {
					var targetX = 0;
					var targetY = 0;

					if (!innerStore.willDelete) {
						// Let node animate to right-bottom corner, cooperating with fadeout,
						// which is appropriate for user understanding.
						// Divided by 2 for reRoot rolling up effect.
						targetX = innerStore.nodeWidth / 2;
						targetY = innerStore.nodeHeight / 2;
					}

					target = storageName === 'nodeGroup' ? {
						x: targetX,
						y: targetY,
						style: {
							opacity: 0
						}
					} : {
						shape: {
							x: targetX,
							y: targetY,
							width: 0,
							height: 0
						},
						style: {
							opacity: 0
						}
					};
				} // TODO: do not support delay until necessary.

				target && animationWrap.add(el, target, duration, 0, easing);
			});
		}); // Make other animations

		each(this._storage, function (store, storageName) {
			each(store, function (el, rawIndex) {
				var last = renderResult.lastsForAnimation[storageName][rawIndex];
				var target = {};

				if (!last) {
					return;
				}

				if (el instanceof graphic.Group) {
					if (last.oldX != null) {
						target.x = el.x;
						target.y = el.y;
						el.x = last.oldX;
						el.y = last.oldY;
					}
				} else {
					if (last.oldShape) {
						target.shape = extend({}, el.shape);
						el.setShape(last.oldShape);
					}

					if (last.fadein) {
						el.setStyle('opacity', 0);
						target.style = {
							opacity: 1
						};
					} // When animation is stopped for succedent animation starting,
					// el.style.opacity might not be 1
					else if (el.style.opacity !== 1) {
						target.style = {
							opacity: 1
						};
					}
				}

				animationWrap.add(el, target, duration, 0, easing);
			});
		}, this);
		this._state = 'animating';
		animationWrap.finished(bind(function () {
			this._state = 'ready';
			renderResult.renderFinally();
		}, this)).start();
	};

	TreemapView.prototype._resetController = function (api) {
		var controller = this._controller; // Init controller.

		if (!controller) {
			controller = this._controller = new RoamController(api.getZr());
			controller.enable(this.seriesModel.get('roam'));
			controller.on('pan', bind(this._onPan, this));
			controller.on('zoom', bind(this._onZoom, this));
		}

		var rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());
		controller.setPointerChecker(function (e, x, y) {
			return rect.contain(x, y);
		});
	};

	TreemapView.prototype._clearController = function () {
		var controller = this._controller;

		if (controller) {
			controller.dispose();
			controller = null;
		}
	};

	TreemapView.prototype._onPan = function (e) {
		if (this._state !== 'animating' && (Math.abs(e.dx) > DRAG_THRESHOLD || Math.abs(e.dy) > DRAG_THRESHOLD)) {
			// These param must not be cached.
			var root = this.seriesModel.getData().tree.root;

			if (!root) {
				return;
			}

			var rootLayout = root.getLayout();

			if (!rootLayout) {
				return;
			}

			this.api.dispatchAction({
				type: 'treemapMove',
				from: this.uid,
				seriesId: this.seriesModel.id,
				rootRect: {
					x: rootLayout.x + e.dx,
					y: rootLayout.y + e.dy,
					width: rootLayout.width,
					height: rootLayout.height
				}
			});
		}
	};

	TreemapView.prototype._onZoom = function (e) {
		var mouseX = e.originX;
		var mouseY = e.originY;

		if (this._state !== 'animating') {
			// These param must not be cached.
			var root = this.seriesModel.getData().tree.root;

			if (!root) {
				return;
			}

			var rootLayout = root.getLayout();

			if (!rootLayout) {
				return;
			}

			var rect = new BoundingRect(rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height);
			var layoutInfo = this.seriesModel.layoutInfo; // Transform mouse coord from global to containerGroup.

			mouseX -= layoutInfo.x;
			mouseY -= layoutInfo.y; // Scale root bounding rect.

			var m = matrix.create();
			matrix.translate(m, m, [-mouseX, -mouseY]);
			matrix.scale(m, m, [e.scale, e.scale]);
			matrix.translate(m, m, [mouseX, mouseY]);
			rect.applyTransform(m);
			this.api.dispatchAction({
				type: 'treemapRender',
				from: this.uid,
				seriesId: this.seriesModel.id,
				rootRect: {
					x: rect.x,
					y: rect.y,
					width: rect.width,
					height: rect.height
				}
			});
		}
	};

	TreemapView.prototype._initEvents = function (containerGroup) {
		var _this = this;

		containerGroup.on('click', function (e) {
			if (_this._state !== 'ready') {
				return;
			}

			var nodeClick = _this.seriesModel.get('nodeClick', true);

			if (!nodeClick) {
				return;
			}

			var targetInfo = _this.findTarget(e.offsetX, e.offsetY);

			if (!targetInfo) {
				return;
			}

			var node = targetInfo.node;

			if (node.getLayout().isLeafRoot) {
				_this._rootToNode(targetInfo);
			} else {
				if (nodeClick === 'zoomToNode') {
					_this._zoomToNode(targetInfo);
				} else if (nodeClick === 'link') {
					var itemModel = node.hostTree.data.getItemModel(node.dataIndex);
					var link = itemModel.get('link', true);
					var linkTarget = itemModel.get('target', true) || 'blank';
					link && windowOpen(link, linkTarget);
				}
			}
		}, this);
	};

	TreemapView.prototype._renderBreadcrumb = function (seriesModel, api, targetInfo) {
		var _this = this;

		if (!targetInfo) {
			targetInfo = seriesModel.get('leafDepth', true) != null ? {
				node: seriesModel.getViewRoot()
			} // FIXME
			// better way?
			// Find breadcrumb tail on center of containerGroup.
				: this.findTarget(api.getWidth() / 2, api.getHeight() / 2);

			if (!targetInfo) {
				targetInfo = {
					node: seriesModel.getData().tree.root
				};
			}
		}

		(this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group))).render(seriesModel, api, targetInfo.node, function (node) {
			if (_this._state !== 'animating') {
				helper.aboveViewRoot(seriesModel.getViewRoot(), node) ? _this._rootToNode({
					node: node
				}) : _this._zoomToNode({
					node: node
				});
			}
		});
	};
	/**
   * @override
   */

	TreemapView.prototype.remove = function () {
		this._clearController();

		this._containerGroup && this._containerGroup.removeAll();
		this._storage = createStorage();
		this._state = 'ready';
		this._breadcrumb && this._breadcrumb.remove();
	};

	TreemapView.prototype.dispose = function () {
		this._clearController();
	};

	TreemapView.prototype._zoomToNode = function (targetInfo) {
		this.api.dispatchAction({
			type: 'treemapZoomToNode',
			from: this.uid,
			seriesId: this.seriesModel.id,
			targetNode: targetInfo.node
		});
	};

	TreemapView.prototype._rootToNode = function (targetInfo) {
		this.api.dispatchAction({
			type: 'treemapRootToNode',
			from: this.uid,
			seriesId: this.seriesModel.id,
			targetNode: targetInfo.node
		});
	};
	/**
   * @public
   * @param {number} x Global coord x.
   * @param {number} y Global coord y.
   * @return {Object} info If not found, return undefined;
   * @return {number} info.node Target node.
   * @return {number} info.offsetX x refer to target node.
   * @return {number} info.offsetY y refer to target node.
   */

	TreemapView.prototype.findTarget = function (x, y) {
		var targetInfo;
		var viewRoot = this.seriesModel.getViewRoot();
		viewRoot.eachNode({
			attr: 'viewChildren',
			order: 'preorder'
		}, function (node) {
			var bgEl = this._storage.background[node.getRawIndex()]; // If invisible, there might be no element.

			if (bgEl) {
				var point = bgEl.transformCoordToLocal(x, y);
				var shape = bgEl.shape; // For performance consideration, don't use 'getBoundingRect'.

				if (shape.x <= point[0] && point[0] <= shape.x + shape.width && shape.y <= point[1] && point[1] <= shape.y + shape.height) {
					targetInfo = {
						node: node,
						offsetX: point[0],
						offsetY: point[1]
					};
				} else {
					return false; // Suppress visit subtree.
				}
			}
		}, this);
		return targetInfo;
	};

	TreemapView.type = 'treemap';
	return TreemapView;
}(ChartView);
/**
 * @inner
 */

function createStorage() {
	return {
		nodeGroup: [],
		background: [],
		content: []
	};
}
/**
 * @inner
 * @return Return undefined means do not travel further.
 */

function renderNode(seriesModel, thisStorage, oldStorage, reRoot, lastsForAnimation, willInvisibleEls, thisNode, oldNode, parentGroup, depth) {
	// Whether under viewRoot.
	if (!thisNode) {
		// Deleting nodes will be performed finally. This method just find
		// element from old storage, or create new element, set them to new
		// storage, and set styles.
		return;
	} // -------------------------------------------------------------------
	// Start of closure variables available in "Procedures in renderNode".

	var thisLayout = thisNode.getLayout();
	var data = seriesModel.getData();
	var nodeModel = thisNode.getModel(); // Only for enabling highlight/downplay. Clear firstly.
	// Because some node will not be rendered.

	data.setItemGraphicEl(thisNode.dataIndex, null);

	if (!thisLayout || !thisLayout.isInView) {
		return;
	}

	var thisWidth = thisLayout.width;
	var thisHeight = thisLayout.height;
	var borderWidth = thisLayout.borderWidth;
	var thisInvisible = thisLayout.invisible;
	var thisRawIndex = thisNode.getRawIndex();
	var oldRawIndex = oldNode && oldNode.getRawIndex();
	var thisViewChildren = thisNode.viewChildren;
	var upperHeight = thisLayout.upperHeight;
	var isParent = thisViewChildren && thisViewChildren.length;
	var itemStyleNormalModel = nodeModel.getModel('itemStyle');
	var itemStyleEmphasisModel = nodeModel.getModel(['emphasis', 'itemStyle']);
	var itemStyleBlurModel = nodeModel.getModel(['blur', 'itemStyle']);
	var itemStyleSelectModel = nodeModel.getModel(['select', 'itemStyle']);
	var borderRadius = itemStyleNormalModel.get('borderRadius') || 0; // End of closure ariables available in "Procedures in renderNode".
	// -----------------------------------------------------------------
	// Node group

	var group = giveGraphic('nodeGroup', Group);

	if (!group) {
		return;
	}

	parentGroup.add(group); // x,y are not set when el is above view root.

	group.x = thisLayout.x || 0;
	group.y = thisLayout.y || 0;
	group.markRedraw();
	inner(group).nodeWidth = thisWidth;
	inner(group).nodeHeight = thisHeight;

	if (thisLayout.isAboveViewRoot) {
		return group;
	} // Background

	var bg = giveGraphic('background', Rect, depth, Z2_BG);
	bg && renderBackground(group, bg, isParent && thisLayout.upperLabelHeight);
	var emphasisModel = nodeModel.getModel('emphasis');
	var focus = emphasisModel.get('focus');
	var blurScope = emphasisModel.get('blurScope');
	var isDisabled = emphasisModel.get('disabled');
	var focusOrIndices = focus === 'ancestor' ? thisNode.getAncestorsIndices() : focus === 'descendant' ? thisNode.getDescendantIndices() : focus; // No children, render content.

	if (isParent) {
		// Because of the implementation about "traverse" in graphic hover style, we
		// can not set hover listener on the "group" of non-leaf node. Otherwise the
		// hover event from the descendents will be listenered.
		if (isHighDownDispatcher(group)) {
			setAsHighDownDispatcher(group, false);
		}

		if (bg) {
			setAsHighDownDispatcher(bg, !isDisabled); // Only for enabling highlight/downplay.

			data.setItemGraphicEl(thisNode.dataIndex, bg);
			enableHoverFocus(bg, focusOrIndices, blurScope);
		}
	} else {
		var content = giveGraphic('content', Rect, depth, Z2_CONTENT);
		content && renderContent(group, content);
		bg.disableMorphing = true;

		if (bg && isHighDownDispatcher(bg)) {
			setAsHighDownDispatcher(bg, false);
		}

		setAsHighDownDispatcher(group, !isDisabled); // Only for enabling highlight/downplay.

		data.setItemGraphicEl(thisNode.dataIndex, group);
		enableHoverFocus(group, focusOrIndices, blurScope);
	}

	return group; // ----------------------------
	// | Procedures in renderNode |
	// ----------------------------

	function renderBackground(group, bg, useUpperLabel) {
		var ecData = getECData(bg); // For tooltip.

		ecData.dataIndex = thisNode.dataIndex;
		ecData.seriesIndex = seriesModel.seriesIndex;
		bg.setShape({
			x: 0,
			y: 0,
			width: thisWidth,
			height: thisHeight,
			r: borderRadius
		});

		if (thisInvisible) {
			// If invisible, do not set visual, otherwise the element will
			// change immediately before animation. We think it is OK to
			// remain its origin color when moving out of the view window.
			processInvisible(bg);
		} else {
			bg.invisible = false;
			var style = thisNode.getVisual('style');
			var visualBorderColor = style.stroke;
			var normalStyle = getItemStyleNormal(itemStyleNormalModel);
			normalStyle.fill = visualBorderColor;
			var emphasisStyle = getStateItemStyle(itemStyleEmphasisModel);
			emphasisStyle.fill = itemStyleEmphasisModel.get('borderColor');
			var blurStyle = getStateItemStyle(itemStyleBlurModel);
			blurStyle.fill = itemStyleBlurModel.get('borderColor');
			var selectStyle = getStateItemStyle(itemStyleSelectModel);
			selectStyle.fill = itemStyleSelectModel.get('borderColor');

			if (useUpperLabel) {
				var upperLabelWidth = thisWidth - 2 * borderWidth;
				prepareText( // PENDING: convert ZRColor to ColorString for text.
					bg, visualBorderColor, style.opacity, {
						x: borderWidth,
						y: 0,
						width: upperLabelWidth,
						height: upperHeight
					});
			} // For old bg.
			else {
				bg.removeTextContent();
			}

			bg.setStyle(normalStyle);
			bg.ensureState('emphasis').style = emphasisStyle;
			bg.ensureState('blur').style = blurStyle;
			bg.ensureState('select').style = selectStyle;
			setDefaultStateProxy(bg);
		}

		group.add(bg);
	}

	function renderContent(group, content) {
		var ecData = getECData(content); // For tooltip.

		ecData.dataIndex = thisNode.dataIndex;
		ecData.seriesIndex = seriesModel.seriesIndex;
		var contentWidth = Math.max(thisWidth - 2 * borderWidth, 0);
		var contentHeight = Math.max(thisHeight - 2 * borderWidth, 0);
		content.culling = true;
		content.setShape({
			x: borderWidth,
			y: borderWidth,
			width: contentWidth,
			height: contentHeight,
			r: borderRadius
		});

		if (thisInvisible) {
			// If invisible, do not set visual, otherwise the element will
			// change immediately before animation. We think it is OK to
			// remain its origin color when moving out of the view window.
			processInvisible(content);
		} else {
			content.invisible = false;
			var nodeStyle = thisNode.getVisual('style');
			var visualColor = nodeStyle.fill;
			var normalStyle = getItemStyleNormal(itemStyleNormalModel);
			normalStyle.fill = visualColor;
			normalStyle.decal = nodeStyle.decal;
			var emphasisStyle = getStateItemStyle(itemStyleEmphasisModel);
			var blurStyle = getStateItemStyle(itemStyleBlurModel);
			var selectStyle = getStateItemStyle(itemStyleSelectModel); // PENDING: convert ZRColor to ColorString for text.

			prepareText(content, visualColor, nodeStyle.opacity, null);
			content.setStyle(normalStyle);
			content.ensureState('emphasis').style = emphasisStyle;
			content.ensureState('blur').style = blurStyle;
			content.ensureState('select').style = selectStyle;
			setDefaultStateProxy(content);
		}

		group.add(content);
	}

	function processInvisible(element) {
		// Delay invisible setting utill animation finished,
		// avoid element vanish suddenly before animation.
		!element.invisible && willInvisibleEls.push(element);
	}

	function prepareText(rectEl, visualColor, visualOpacity, // Can be null/undefined
		upperLabelRect) {
		var normalLabelModel = nodeModel.getModel(upperLabelRect ? PATH_UPPERLABEL_NORMAL : PATH_LABEL_NOAMAL);
		var defaultText = convertOptionIdName(nodeModel.get('name'), null);
		var isShow = normalLabelModel.getShallow('show');
		setLabelStyle(rectEl, getLabelStatesModels(nodeModel, upperLabelRect ? PATH_UPPERLABEL_NORMAL : PATH_LABEL_NOAMAL), {
			defaultText: isShow ? defaultText : null,
			inheritColor: visualColor,
			defaultOpacity: visualOpacity,
			labelFetcher: seriesModel,
			labelDataIndex: thisNode.dataIndex
		});
		var textEl = rectEl.getTextContent();

		if (!textEl) {
			return;
		}

		var textStyle = textEl.style;
		var textPadding = normalizeCssArray(textStyle.padding || 0);

		if (upperLabelRect) {
			rectEl.setTextConfig({
				layoutRect: upperLabelRect
			});
			textEl.disableLabelLayout = true;
		}

		textEl.beforeUpdate = function () {
			var width = Math.max((upperLabelRect ? upperLabelRect.width : rectEl.shape.width) - textPadding[1] - textPadding[3], 0);
			var height = Math.max((upperLabelRect ? upperLabelRect.height : rectEl.shape.height) - textPadding[0] - textPadding[2], 0);

			if (textStyle.width !== width || textStyle.height !== height) {
				textEl.setStyle({
					width: width,
					height: height
				});
			}
		};

		textStyle.truncateMinChar = 2;
		textStyle.lineOverflow = 'truncate';
		addDrillDownIcon(textStyle, upperLabelRect, thisLayout);
		var textEmphasisState = textEl.getState('emphasis');
		addDrillDownIcon(textEmphasisState ? textEmphasisState.style : null, upperLabelRect, thisLayout);
	}

	function addDrillDownIcon(style, upperLabelRect, thisLayout) {
		var text = style ? style.text : null;

		if (!upperLabelRect && thisLayout.isLeafRoot && text != null) {
			var iconChar = seriesModel.get('drillDownIcon', true);
			style.text = iconChar ? iconChar + ' ' + text : text;
		}
	}

	function giveGraphic(storageName, Ctor, depth, z) {
		var element = oldRawIndex != null && oldStorage[storageName][oldRawIndex];
		var lasts = lastsForAnimation[storageName];

		if (element) {
			// Remove from oldStorage
			oldStorage[storageName][oldRawIndex] = null;
			prepareAnimationWhenHasOld(lasts, element);
		} // If invisible and no old element, do not create new element (for optimizing).
		else if (!thisInvisible) {
			element = new Ctor();

			if (element instanceof Displayable) {
				element.z2 = calculateZ2(depth, z);
			}

			prepareAnimationWhenNoOld(lasts, element);
		} // Set to thisStorage

		return thisStorage[storageName][thisRawIndex] = element;
	}

	function prepareAnimationWhenHasOld(lasts, element) {
		var lastCfg = lasts[thisRawIndex] = {};

		if (element instanceof Group) {
			lastCfg.oldX = element.x;
			lastCfg.oldY = element.y;
		} else {
			lastCfg.oldShape = extend({}, element.shape);
		}
	} // If a element is new, we need to find the animation start point carefully,
	// otherwise it will looks strange when 'zoomToNode'.

	function prepareAnimationWhenNoOld(lasts, element) {
		var lastCfg = lasts[thisRawIndex] = {};
		var parentNode = thisNode.parentNode;
		var isGroup = element instanceof graphic.Group;

		if (parentNode && (!reRoot || reRoot.direction === 'drillDown')) {
			var parentOldX = 0;
			var parentOldY = 0; // New nodes appear from right-bottom corner in 'zoomToNode' animation.
			// For convenience, get old bounding rect from background.

			var parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()];

			if (!reRoot && parentOldBg && parentOldBg.oldShape) {
				parentOldX = parentOldBg.oldShape.width;
				parentOldY = parentOldBg.oldShape.height;
			} // When no parent old shape found, its parent is new too,
			// so we can just use {x:0, y:0}.

			if (isGroup) {
				lastCfg.oldX = 0;
				lastCfg.oldY = parentOldY;
			} else {
				lastCfg.oldShape = {
					x: parentOldX,
					y: parentOldY,
					width: 0,
					height: 0
				};
			}
		} // Fade in, user can be aware that these nodes are new.

		lastCfg.fadein = !isGroup;
	}
} // We cannot set all background with the same z, because the behaviour of
// drill down and roll up differ background creation sequence from tree
// hierarchy sequence, which cause lower background elements to overlap
// upper ones. So we calculate z based on depth.
// Moreover, we try to shrink down z interval to [0, 1] to avoid that
// treemap with large z overlaps other components.

function calculateZ2(depth, z2InLevel) {
	return depth * Z2_BASE + z2InLevel;
}

export default TreemapView;